package base;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.File;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;

import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.UIManager;

public abstract class GUI extends JFrame implements Refreshable {
	private String userHome, fileSeparator;
	private File appFolder, templateFolder;
	private AboutBox aboutBox = new AboutBox();
	private TextFrame handbookBox = new TextFrame("documents/handbook.html", "Stones Handbook", "text/html");
	private JLabel message = new JLabel();
	private JPanel statusBar = new JPanel();
	private ZoomDialog zoomDialog;
	private BoardWidget boardWidget;
	private JScrollPane display;
	protected JMenuBar menuBar;
	protected JMenu fileMenu, editMenu, viewMenu, helpMenu;
	private JMenuItem newFile, openFile, saveFile, quit, zoom, zoomIn, zoomOut, handbook, about;
	private Piece selectedPiece;
	protected MouseListener mouseListener = new MouseListener() {
		
		@Override
		public void mouseClicked(MouseEvent e) {}
		@Override
		public void mouseEntered(MouseEvent e) {}
		@Override
		public void mouseExited(MouseEvent e) {}
		@Override
		public void mousePressed(MouseEvent e) {}
		@Override
		public void mouseReleased(MouseEvent e) {
			Point point = getBoardWidget().getLocation(new Point(e.getX(), e.getY()));
			
			if (getBoardWidget().getBoard().contains(point)) {
				Square square = getBoardWidget().getBoard().getSquare(point);
				setFocus(point);
				
				if (e.getButton() == MouseEvent.BUTTON1 && !isSelectedPiece()) {
					select(square);
				}
				else if (e.getButton() == MouseEvent.BUTTON1 && isSelectedPiece()) {
					move(square.getLocation());
				}
				else if (e.getButton() == MouseEvent.BUTTON3) {
					select(square);
				}
			}
		}
	};
	
	protected KeyListener keyListener = new KeyListener() {
		
		@Override
		public void keyPressed(KeyEvent e) {
			if (getBoardWidget() != null) {
				Square focusedSquare = getBoardWidget().getFocusedSquare();
				int x = focusedSquare.getLocation().x, y = focusedSquare.getLocation().y;
				
				switch (e.getKeyCode()) {
				case KeyEvent.VK_UP:
					setFocus(new Point(x, y - 1));
					break;
				case KeyEvent.VK_DOWN:
					setFocus(new Point(x, y + 1));
					break;
				case KeyEvent.VK_LEFT:
					setFocus(new Point(x - 1, y));
					break;
				case KeyEvent.VK_RIGHT:
					setFocus(new Point(x + 1, y));
					break;
				case KeyEvent.VK_ENTER:
				case KeyEvent.VK_SPACE:
					if (!isSelectedPiece()) {
						select(focusedSquare);
					}
					else {
						move(focusedSquare.getLocation());
					}
					break;
				case KeyEvent.VK_ESCAPE:
					select(null);
					break;
				case KeyEvent.VK_EQUALS:
					if (e.getModifiersEx() == KeyEvent.CTRL_DOWN_MASK) {
						zoomIn();
					}
					break;
				case KeyEvent.VK_MINUS:
					if (e.getModifiersEx() == KeyEvent.CTRL_DOWN_MASK) {
						zoomOut();
					}
					break;
				}
				
				refresh();
			}
		}
		
		@Override
		public void keyReleased(KeyEvent e) {}
		@Override
		public void keyTyped(KeyEvent e) {}
	};
	
	protected ActionListener menuListener = new ActionListener() {

		@Override
		public void actionPerformed(ActionEvent e) {
			if (e.getActionCommand().equals(newFile.getActionCommand())) {
				newGame();
			}
			else if (e.getActionCommand().equals(openFile.getActionCommand())) {
				open();
			}
			else if (e.getActionCommand().equals(saveFile.getActionCommand())) {
				save();
			}
			else if (e.getActionCommand().equals(quit.getActionCommand())) {
				System.exit(0);
			}
			else if (e.getActionCommand().equals(zoom.getActionCommand())) {
				getZoomDialog().setVisible(true);
				
				refresh();
			}
			else if (e.getActionCommand().equals(zoomIn.getActionCommand())) {
				zoomIn();
			}
			else if (e.getActionCommand().equals(zoomOut.getActionCommand())) {
				zoomOut();
			}
			else if (e.getActionCommand().equals(about.getActionCommand())) {
				aboutBox.setVisible(true);
			}
			else if (e.getActionCommand().equals(handbook.getActionCommand())) {
				handbookBox.setVisible(true);
			}
		}
		
	};
	
	private void initializeDirectories() {
		this.setUserHome(System.getProperty("user.home"));
		this.setFileSeparator(System.getProperty("file.separator"));
		String configFolder = this.getUserHome() + this.getFileSeparator() + ".config";
		String appFolder = configFolder + this.getFileSeparator() + "stones-game";
		
		File config = new File(configFolder);
		
		if (!config.exists() && !config.mkdir()) {
			this.displayMessage("User configuration folder isn't available. Some features may not work.");
		}
		
		this.setAppFolder(new File(appFolder));
		
		if (!this.getAppFolder().exists() && !this.getAppFolder().mkdir()) {
			this.displayMessage("User configuration folder isn't available. Some features may not work.");
		}
		
		this.setTemplateFolder(new File(this.getAppFolder().getPath() + this.getFileSeparator() + "templates"));
		
		if (!this.getTemplateFolder().exists() && !this.getTemplateFolder().mkdir()) {
			this.displayMessage("Template folder isn't available. Some features may not work.");
		}
	}
	
	public GUI() {
		this.initializeDirectories();
		
		this.addKeyListener(this.keyListener);
		this.menuBar = new JMenuBar();
		
		this.fileMenu = new JMenu("File");
		this.newFile = new JMenuItem("New");
		this.openFile = new JMenuItem("Open");
		this.saveFile = new JMenuItem("Save");
		this.quit = new JMenuItem("Quit");
		
		this.newFile.setActionCommand("New");
		this.openFile.setActionCommand("Open");
		this.saveFile.setActionCommand("Save");
		this.quit.setActionCommand("Quit");
		
		this.newFile.addActionListener(this.menuListener);
		this.openFile.addActionListener(this.menuListener);
		this.saveFile.addActionListener(this.menuListener);
		this.quit.addActionListener(this.menuListener);
		
		this.fileMenu.add(this.newFile);
		this.fileMenu.add(this.openFile);
		this.fileMenu.add(this.saveFile);
		this.fileMenu.add(this.quit);
		
		this.editMenu = new JMenu("Edit");
		
		this.viewMenu = new JMenu("View");
		this.zoom = new JMenuItem("Zoom...");
		this.zoomIn = new JMenuItem("Zoom In");
		this.zoomOut= new JMenuItem("Zoom Out");
		
		this.zoom.setActionCommand("Zoom...");
		this.zoomIn.setActionCommand("Zoom In");
		this.zoomOut.setActionCommand("Zoom Out");
		
		this.zoom.addActionListener(this.menuListener);
		this.zoomIn.addActionListener(this.menuListener);
		this.zoomOut.addActionListener(this.menuListener);
		
		this.viewMenu.add(this.zoom);
		this.viewMenu.add(this.zoomIn);
		this.viewMenu.add(this.zoomOut);
		
		this.helpMenu = new JMenu("Help");
		this.handbook = new JMenuItem("Stones Handbook");
		this.about = new JMenuItem("About");
		
		this.handbook.setActionCommand("Stones Handbook");
		this.about.setActionCommand("About");
		
		this.handbook.addActionListener(this.menuListener);
		this.about.addActionListener(this.menuListener);
		
		this.helpMenu.add(this.handbook);
		this.helpMenu.add(this.about);
		
		this.menuBar.add(this.fileMenu);
		this.menuBar.add(this.editMenu);
		this.menuBar.add(this.viewMenu);
		this.menuBar.add(this.helpMenu);
		this.setJMenuBar(this.menuBar);
		
		this.fileMenu.setMnemonic(KeyEvent.VK_F);
		this.newFile.setMnemonic(KeyEvent.VK_N);
		this.openFile.setMnemonic(KeyEvent.VK_O);
		this.saveFile.setMnemonic(KeyEvent.VK_S);
		this.quit.setMnemonic(KeyEvent.VK_Q);
		
		this.editMenu.setMnemonic(KeyEvent.VK_E);
		
		this.viewMenu.setMnemonic(KeyEvent.VK_V);
		this.zoom.setMnemonic(KeyEvent.VK_Z);
		this.zoomIn.setMnemonic(KeyEvent.VK_I);
		this.zoomOut.setMnemonic(KeyEvent.VK_O);
		
		this.helpMenu.setMnemonic(KeyEvent.VK_H);
		this.handbook.setMnemonic(KeyEvent.VK_H);
		this.about.setMnemonic(KeyEvent.VK_A);
		
		this.getContentPane().setLayout(new BorderLayout());
		this.statusBar.setLayout(new FlowLayout(FlowLayout.LEADING));
		
		this.statusBar.add(this.message);
		this.getContentPane().add(this.statusBar, BorderLayout.SOUTH);
		this.displayMessage(" ");
		
		this.setSize(500, 520);
		this.setLocationByPlatform(true);
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}
	
	protected ZoomDialog getZoomDialog() {
		return this.zoomDialog;
	}

	protected abstract void move(Point destination);

	protected abstract void select(Square square);

	protected abstract boolean isSelectedPiece();
	
	protected void setSelectedPiece(Piece piece) {
		this.selectedPiece = piece;
	}
	
	protected Piece getSelectedPiece() {
		return this.selectedPiece;
	}

	protected void setFocus(Point point) {
		if (this.getBoardWidget().getBoard().contains(point)) {
			this.getBoardWidget().setFocusedSquare(point);
			
			Square focusedSquare = this.getBoardWidget().getFocusedSquare();
			Rectangle rectangle = this.getBoardWidget().getVisibleRect();
			int vert = (focusedSquare.getLocation().y * this.getBoardWidget().getZoom()) - (rectangle.height / 2);
			int hori = focusedSquare.getLocation().x * this.getBoardWidget().getZoom() - (rectangle.width / 2);
			this.display.getVerticalScrollBar().setValue(vert);
			this.display.getHorizontalScrollBar().setValue(hori);
		}
	}

	public BoardWidget getBoardWidget() {
		return this.boardWidget;
	}
	
	public void refresh() {
		if (this.getBoardWidget() != null) {
			this.getBoardWidget().revalidate();
			this.getBoardWidget().repaint();
		}
		
		this.validate();
	}
	
	protected abstract void newGame();
	
	protected abstract void open();
	
	protected abstract void save();
	
	protected void setBoardWidget(BoardWidget boardWidget) {
		this.boardWidget = boardWidget;
		
		this.getBoardWidget().setZoom(50);
		Dimension size = this.getBoardWidget().getBoard().getSize();
		this.getBoardWidget().setFocusedSquare(new Point((size.width - 1) / 2, (size.getSize().height - 1) / 2));
		this.getBoardWidget().addMouseListener(this.mouseListener);
		
		if (this.display != null) {
			this.getContentPane().remove(this.display);
		}
		
		JPanel panel = new JPanel();
		panel.setLayout(new GridBagLayout());
		panel.add(this.getBoardWidget());
		this.display = new JScrollPane(panel);
		
		this.getContentPane().add(this.display, BorderLayout.CENTER);
		
		if (this.zoomDialog == null) {
			this.zoomDialog = new ZoomDialog(this);
		}
		
		this.zoomDialog.setBoardWidget(this.getBoardWidget());
		
		this.refresh();
	}
	
	protected void zoomIn() {
		int zoom = getBoardWidget().getZoom() + 25;
		
		if (zoom <= 360) {
			getBoardWidget().setZoom(zoom);
		}
		
		refresh();
	}
	
	protected void zoomOut() {
		int zoom = getBoardWidget().getZoom() - 25;
		
		if (zoom >= 25) {
			getBoardWidget().setZoom(zoom);
		}
		
		refresh();
	}
	
	protected void displayMessage(String string) {
		this.message.setText(string);
	}
	
	static Image getImage(String path) {
        URL url = GamePlayer.class.getResource(path);
        
        if (url != null) {
            return new ImageIcon(url).getImage();
        } else {
            return null;
        }
    }
	
//	Since getFile fails to fetch the file I want when it happens to be in a jar file, I'm using InputStream as a workaround.
//	TODO Stop using InputStream.
	static InputStream getInputStream(String path) {
        return GamePlayer.class.getResourceAsStream(path);
    }
	
	static File getFile(String path) throws URISyntaxException {
        return new File(GamePlayer.class.getResource(path).toURI());
    }

	private void setUserHome(String userHome) {
		this.userHome = userHome;
	}

	public String getUserHome() {
		return userHome;
	}

	private void setFileSeparator(String fileSeparator) {
		this.fileSeparator = fileSeparator;
	}

	public String getFileSeparator() {
		return fileSeparator;
	}

	private void setAppFolder(File configFolderFile) {
		this.appFolder = configFolderFile;
	}

	public File getAppFolder() {
		return appFolder;
	}

	private void setTemplateFolder(File templateFolder) {
		this.templateFolder = templateFolder;
	}

	public File getTemplateFolder() {
		return templateFolder;
	}
}